在開發的時候有時需要存一些我們產生出來的類別型態到 SQLite 中,但 SQLite 本身並不支援這種型態,因此在寫入前需要先做一次轉換,讓他變成 SQLite 支援的型態
例如 SQLite 並不支援 Date
或是 DataTime
的格式,但是有支援 Long
型態,因此在寫入之前可以先把 Date 轉成 Long 的 milliseconds
,反之,在讀出來之後也要先把 Long 的 milliseconds
轉成 Date 再做使用
那在寫這種轉換時,需要在函式前面加上 @TypeConverter
裝飾詞, Room
會根據這個裝飾詞轉
class Converters {
// 讀 milliseconds 出來轉成 Date
@TypeConverter
fun convertToEntityProperty(databaseValue: Long): Date {
return Date(databaseValue)
}
// 把 Date 轉成 milliseconds 再存進去
@TypeConverter
fun convertToDatabaseValue(entityProperty: Date): Long {
return entityProperty.time
}
}
在定義完 Converter
類別後,就可以把這樣的規則加註到 Room Database
上,當 Room 在做存取的時候,就會依照這個規則下去做轉換
@Database(entities = [PirateInfoDao::class], version = 1, exportSchema = true)
@TypeConverters(Converters::class) // 把 Converters 加在這邊告訴 Room 有哪些需要被轉換
abstract class AppDatabase : RoomDatabase() {
// 省略...
}
但是!但是!但是!
在我開開心心的把 App 跑起來的時候發現不太妙的事情,我以前這麼做都可以正常運作,但因為這次導入了我不熟悉的 Moshi
,因此在運行時一直有報錯的狀況,即使去做了一些調整,但還是沒把 Bug 解掉,因此我只好在快到 12 點之前先放棄了...
之後會在把這塊補上來,或是有大大可以給出一些建議,指點指點迷津
那接下來就是要把資料存到 Room 裏面就算是完成今天的進度囉~
那我希望是在使用者執行 PirateDetailRepository 中的 fetchPirateInfo() ,就把資料順勢存到 SQLite 中,因此首先當然是先去加上 Insert 到 fetchPirateInfo()
中
那從下面的 Code 可以觀察到有幾個改變
private val pirateInfoDao: PirateInfoDao
pirateInfoDao.insertPirateInfo(pirateInfo)
,將物件 insert
到 Database 去// PirateDetailRepository.kt
// 加上了 Primary Constructor ,參數是 PirateInfoDao
class PirateDetailRepository(private val pirateInfoDao: PirateInfoDao) {
private val pirateService: PirateService =
NetworkManager.provideRetrofit(NetworkManager.provideOkHttpClient())
.create(PirateService::class.java)
suspend fun fetchPirateInfo(
pirateId: Int,
onSuccess: () -> Unit,
onError: (String) -> Unit
) = flow<PirateInfo> {
val response = pirateService.fetchPirateInfo(pirateId)
response.suspendOnSuccess {
if (data != null) {
val pirateInfo: PirateInfo = data ?: PirateInfo(
id = 0,
name = "",
height = 0,
weight = 0,
attack = 0
)
pirateInfoDao.insertPirateInfo(pirateInfo)
emit(pirateInfo)
onSuccess()
}
}.onError {
onError(message())
}.onException {
onError(message())
}
}.flowOn(Dispatchers.IO)
}
那對應的 ViewModel
的部份也需要做一些小調整
pirateDetailRepository
,型態是 PirateDetailRepository?
init{}
區塊,裡面會宣告 pirateInfoDao
,而初始值是到 AppDatabase
取得 pirateInfoDao()
pirateInfoDao
加到 PirateDetailRepository()
的 Constructor 中class GetPirateViewModel(application: Application) :
AndroidViewModel(application) {
private val pirateDetailRepository: PirateDetailRepository?
fun pirateInfoLiveData(pirateId: Int): LiveData<PirateInfo> =
liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
pirateDetailRepository?.fetchPirateInfo(
pirateId = pirateId,
onSuccess = { },
onError = { }
)?.asLiveData()?.let {
emitSource(it)
}
}
init {
val pirateInfoDao = AppDatabase.getInstance(application)?.pirateInfoDao()
pirateDetailRepository = pirateInfoDao?.let { PirateDetailRepository(it) }
}
}
到這邊就可以把資料存到 Database 裡面去了,但要怎麼確認資料有成功寫進去呢?有兩個方法可以知道
在 Android Studio 4.0 之後是有內建的可視化工具可以確認資料庫目前的內容,但在之前如果要看到資料庫裏面的資料非常麻煩,要去裝個支援 SQLite 的工具,然後想辦法進到手機裏面,把 SQL 檔打包出來在丟進去看,光是要確認一個欄位就構開發者有的受的,因此 Facebook 推出了一個很讚的工具讓我們在開發時不用重複上述複雜的流程,那就是接下來要介紹的 Stetho
Stetho 是 Facebook 開發的一款調適工具,他最大的特色是可以透過 Chrome DevTools 觀看即時的 App 數據
關於該如何使用可以看一篇我以前曾經寫過的介紹
那將程式跑下去,再到 chrom://inspect
中打開 Stetho 監聽的 App,就可以看到資料已經順順的進去資料庫囉~ (歡呼
今天大致上把整個流程已經補的差不多了,但在寫 Code 和寫文章的過程中,還是會來來回回修改,因此文章教學結構上我個人覺得不太流暢,很多地方會感覺跳來跳去,但因為時間壓力的關係,目前只能先這樣子了,之後會在把這邊重構的更加完整,敬請期待,那明天會接續把取得 Pirate List 的 Code 補完
如果內容有任何問題或錯誤,歡迎提出與指教